package com.koi.logs.aspect;

import com.koi.common.anno.KoiLog;
import com.koi.common.constants.KoiConstants;
import com.koi.common.satoken.KoiLoginUtil;
import com.koi.common.utils.ip.IpAddressUtil;
import com.koi.common.utils.json.JsonUtil;
import com.koi.logs.ip.IpUtil;
import com.koi.logs.ip.ServletUtil;
import com.koi.plus.mapper.system.SysOperLogMapper;
import com.koi.pojo.system.SysOperLog;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.MDC;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;

import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author ･ᴗ･
 * @description [KoiLogAspect]
 */
@Aspect // 代表是一个aop切面
@RequiredArgsConstructor
@Component
@Slf4j
@Order(10)
public class KoiLogAspect {

    private final SysOperLogMapper operLogMapper;

    private final IpAddressUtil ipAddressUtil;

    // 1、定义切点，切点标注有注解@KoiLog的的所有函数，通过 @Pointcut 判断才可以进入到具体增强的通知
    @Pointcut("@annotation(com.koi.common.anno.KoiLog)")
    public void logPointCut() {
    }

    // 2、环绕通知（切点或者切点表达式）
    @Around("logPointCut()")
    public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        // 注意：使用aroundAdvice在方法结束后执行，则抛出异常不进行计算时间。
        // 方法执行的开始时间
        long startTime = System.currentTimeMillis();
        // MDC，通过 MDC 实现日志跟踪
        MDC.put("traceId", UUID.randomUUID().toString());
        // 核心代码，这个前后通知分界线
        log.info("[日志记录]-[开始]");

        // 获取被请求的类和方法
        String className = proceedingJoinPoint.getTarget().getClass().getSimpleName();
        String methodName = proceedingJoinPoint.getSignature().getName();

        // 传递过来的标题，操作类型等
        Map<String, Object> operMap = getApiOperationLogDescription(proceedingJoinPoint);

        // 请求入参
        Object[] args = proceedingJoinPoint.getArgs();

        // 入参转 JSON 字符串
        String paramsJsonStr = Arrays.stream(args).map(toJsonStr()).collect(Collectors.joining(", "));

        // 打印请求相关参数
        log.info("日志切面请求开始: [{}], 入参: {}, 请求类: {}, 请求方法: {}", operMap.get("operName"), paramsJsonStr, className, methodName);
        try {
            // 执行切点方法
            Object proceed = proceedingJoinPoint.proceed();

            String resultJsonStr = JsonUtil.toJsonString(proceed);
            if (StringUtils.isNotBlank(resultJsonStr) && resultJsonStr.length() > 1000) {
                resultJsonStr = resultJsonStr.substring(0, 1000);
            }
            if (StringUtils.isNotBlank(paramsJsonStr) && paramsJsonStr.length() > 500) {
                paramsJsonStr = paramsJsonStr.substring(0, 500);
            }

            // 方法执行的结束时间
            long endTime = System.currentTimeMillis();

            // 方法的总耗时
            long executionTime = endTime - startTime;

            // 打印出参等相关信息
            log.info("日志切面请求结束: [{}], 耗时: {}ms", operMap.get("operName"), executionTime);

            /* 添加操作日志 */
            addOperLog("0", operMap.get("operName"), operMap.get("operType"), operMap.get("systemType"), operMap.get("isSaveRequestParams"), operMap.get("isSaveResponseData"), executionTime, className, methodName, paramsJsonStr, resultJsonStr, null);
            return proceed;
        } catch (Throwable ex) {
            String errorMsg = "";
            if (StringUtils.isNotBlank(ex.getMessage()) && ex.getMessage().length() > 255) {
                errorMsg = ex.getMessage().substring(0, 255);
            } else {
                errorMsg = ex.getMessage();
            }
            addOperLog("1", operMap.get("operName"), operMap.get("operType"), operMap.get("systemType"), operMap.get("isSaveRequestParams"), operMap.get("isSaveResponseData"), 0, className, methodName, paramsJsonStr, null, errorMsg);
            throw ex;
        } finally {
            MDC.clear();
        }
    }

    /**
     * @param operName      操作名称
     * @param operType      业务类型，新增、修改、删除等
     * @param systemType    PC端、移动端等
     * @param executionTime 消耗时间
     * @param className     类名
     * @param methodName    方法名
     * @param paramsJsonStr 入参参数
     * @param resultJsonStr 返回结果
     * @description [添加操作日志]
     * @author ･ᴗ･
     */
    private void addOperLog(String operStatus, Object operName, Object operType, Object systemType, Object isSaveRequestParams, Object isSaveReponseData, long executionTime, String className, String methodName, String paramsJsonStr, String resultJsonStr, String errorMsg) {
        SysOperLog sysOperLog = new SysOperLog();
        try {
            sysOperLog.setOperName(operName.toString());
            sysOperLog.setOperType(operType.toString());
            sysOperLog.setMethodName(methodName);
            sysOperLog.setOperMan(KoiLoginUtil.getLoginName() + "/" + KoiLoginUtil.getUserName());
            sysOperLog.setSystemType(systemType.toString());
            String ipAddr = IpUtil.getIpAddr(ServletUtil.getRequest());
            sysOperLog.setOperIp(ipAddr);
            sysOperLog.setOperLocation(ipAddressUtil.getAddress(ipAddr));
            if (isSaveRequestParams.toString().equals("true")) {
                sysOperLog.setOperParam(paramsJsonStr);
            }
            if (isSaveReponseData.toString().equals("true")) {
                sysOperLog.setJsonResult(resultJsonStr);
            }
            if(StringUtils.isNotBlank(operStatus)) {
                sysOperLog.setOperStatus(operStatus);
            } else {
                sysOperLog.setOperStatus(KoiConstants.ONE_STRING);
            }
            sysOperLog.setOperTime(new Date());
            sysOperLog.setCostTime(executionTime + "/ms");
            sysOperLog.setErrorMsg(errorMsg);
            // 操作方法URL
            sysOperLog.setOperIp(IpUtil.getIpAddr(ServletUtil.getRequest()));
            sysOperLog.setOperUrl(ServletUtil.getRequestURI());
            sysOperLog.setRequestMethod(RequestMethod.valueOf(ServletUtil.getMethod()).toString());
        } catch (Exception e) {
            sysOperLog.setOperStatus("1");
            sysOperLog.setErrorMsg(e.getMessage());
        } finally {
            int insert = operLogMapper.insert(sysOperLog);
            if (insert == 0) {
                log.info("日志操作记录失败：operName：{}，className：{}，methodName：{}", operName, className, methodName);
            }
        }

    }

    /**
     * @description [获取注解的描述信息]
     */
    private Map<String, Object> getApiOperationLogDescription(ProceedingJoinPoint joinPoint) {
        // 1、从 ProceedingJoinPoint 获取 MethodSignature
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();

        // 2、使用 MethodSignature 获取当前被注解的 Method
        Method method = signature.getMethod();

        // 3、从 Method 中提取 LogExecution 注解
        KoiLog koiLog = method.getAnnotation(KoiLog.class);

        // 4、从 LogExecution 注解中获取 description 属性
        Map<String, Object> map = new HashMap<>();
        map.put("operName", koiLog.title());
        map.put("operType", koiLog.operType());
        map.put("systemType", koiLog.systemType());
        map.put("isSaveRequestParams", koiLog.isSaveRequestParams());
        map.put("isSaveResponseData", koiLog.isSaveResponseData());
        return map;
    }

    /**
     * @description [转JSON字符串]
     */
    private Function<Object, String> toJsonStr() {
        return arg -> JsonUtil.toJsonString(arg);
    }

}
